#ifndef __CRYMATCHMAKING_H__
#define __CRYMATCHMAKING_H__

#pragma once

#include "ICryMatchMaking.h"
#include "Lobby/CryLobby.h"
#include "Socket/NetAddress.h"
#include "Lobby/CryHostMigration.h"

#define NET_HOST_HINT_DEBUG 0
#define USE_DEFAULT_BAN_TIMEOUT (0.0f)

///////////////////////////////////////////////////////////////////////////
// When all references to SCrySessionUserData and ECrySessionUserDataType have been removed from the engine this can go.
typedef SCryLobbyUserData SCrySessionUserData;
typedef ECryLobbyUserDataType ECrySessionUserDataType;
const ECrySessionUserDataType eCSUDT_Int64 = eCLUDT_Int64;
const ECrySessionUserDataType eCSUDT_Int32 = eCLUDT_Int32;
const ECrySessionUserDataType eCSUDT_Int16 = eCLUDT_Int16;
const ECrySessionUserDataType eCSUDT_Int8 = eCLUDT_Int8;
const ECrySessionUserDataType eCSUDT_Float64 = eCLUDT_Float64;
const ECrySessionUserDataType eCSUDT_Float32 = eCLUDT_Float32;
const ECrySessionUserDataType eCSUDT_Int64NoEndianSwap = eCLUDT_Int64NoEndianSwap;
///////////////////////////////////////////////////////////////////////////

class CCryLobbyPacket;
class CCrySharedLobbyPacket;

const ECryLobbyError eCLE_Pending = eCLE_NumErrors;
const uint32 CryMatchMakingConnectionTimeOut = 10000;
const uint32 CryMatchMakingHostMigratedConnectionConfirmationWindow = 3000; // 3 second window to receive client confirmations before pruning

#define MAX_MATCHMAKING_SESSION_USER_DATA	64
#define MAX_MATCHMAKING_TASKS							8
#define MAX_MATCHMAKING_PARAMS						7
#define GAME_RECV_QUEUE_SIZE							16

#define HOST_MIGRATION_TASK_ID_FLAG (0x10000000)

#define INVALID_USER_ID (0)

typedef uint32 CryMatchMakingTaskID;
const CryMatchMakingTaskID CryMatchMakingInvalidTaskID = 0xffffffff;

typedef uint32 CryMatchMakingConnectionID;
const CryMatchMakingConnectionID CryMatchMakingInvalidConnectionID = 0xffffffff;

struct SCrySharedSessionID : public SCrySessionID
{
	void* operator new(size_t size) throw()
	{
		CCryLobby* pLobby = (CCryLobby*)CCryLobby::GetLobby();

		if (pLobby)
		{
			TMemHdl hdl = pLobby->MemAlloc(size);

			if (hdl != TMemInvalidHdl)
			{
				SCrySharedSessionID* id = (SCrySharedSessionID*)pLobby->MemGetPtr(hdl);

				id->hdl = hdl;

				return id;
			}
		}

		return NULL;
	}

	void operator delete(void* p)
	{
		if (p)
		{
			CCryLobby* pLobby = (CCryLobby*)CCryLobby::GetLobby();

			if (pLobby)
			{
				pLobby->MemFree(((SCrySharedSessionID*)p)->hdl);
			}
		}
	}

	TMemHdl	hdl;
};

#if NETWORK_HOST_MIGRATION
struct SHostMigrationInfo_Private : public SHostMigrationInfo
{
	SHostMigrationInfo_Private(void) : m_startTime(0.0f), m_logTime(0.0f), m_sessionMigrated(false), m_matchMakingFinished(false), m_newHostAddressValid(false), m_lobbyHMStatus(0) {};

	void Reset(void)
	{
		m_sessionMigrated = false;
		m_matchMakingFinished = false;
		m_newHostAddressValid = false;
		m_lobbyHMStatus = 0;
		m_taskID = CryMatchMakingInvalidTaskID;
	}

	CTimeValue m_startTime;
	CTimeValue m_logTime;
	TNetAddress m_newHostAddress;

	_smart_ptr<CNetContextState> m_storedContextState;

	uint32 m_lobbyHMStatus;

	bool m_sessionMigrated;
	bool m_matchMakingFinished;
	bool m_newHostAddressValid;
#if ENABLE_HOST_MIGRATION_STATE_CHECK
	bool m_stateCheckDone;
#endif
};

#if ENABLE_HOST_MIGRATION_STATE_CHECK
#define HMSCD_MAX_ENTITY_NAME_LENGTH	16

enum EHostMigrationStateCheckDataType
{
	eHMSCDT_NID
};

struct SHostMigrationStateCheckDataNID
{
	uint32	aspectsEnabled;
	uint16	id;
	uint16	salt;
	char		name[HMSCD_MAX_ENTITY_NAME_LENGTH];
	bool		allocated;
	bool		controlled;
	bool		owned;
	uint8		spawnType;
};

struct SHostMigrationStateCheckData
{
	EHostMigrationStateCheckDataType		type;

	union
	{
		SHostMigrationStateCheckDataNID		nid;
	};
};
#endif
#endif

struct SCrySessionInfoRequester
{
	TNetAddress						addr;
	uint32								cookie;
	CryMatchMakingTaskID	mmTaskID;
};

struct SBannedUser 
{
	CryUserID		m_userID;
	CTimeValue	m_bannedUntil;
};

typedef std::vector<SBannedUser> TBannedUser;

class CCryMatchMaking : public CMultiThreadRefCount, public ICryMatchMaking
{
public:
																	CCryMatchMaking(CCryLobby* lobby, CCryLobbyService* service, ECryLobbyService serviceType);

					ECryLobbyError					Initialise();

					ECryLobbyError					Terminate();
					void										OnPacket(const TNetAddress& addr, CCryLobbyPacket* pPacket);
					void										OnError(const TNetAddress& addr, ESocketError error, CryLobbySendID sendID);
					void										OnSendComplete(const TNetAddress& addr, CryLobbySendID sendID);
	virtual void										CancelTask(CryLobbyTaskID lTaskID);
					void										SessionDisconnectRemoteConnectionFromNub(CrySessionHandle gh, EDisconnectionCause cause);
	virtual void										SessionJoinFromConsole(void);
	virtual CrySessionID						GetSessionIDFromConsole(void);
	virtual ECryLobbyError					GetSessionAddressFromSessionID( CrySessionID sessionID, uint32& hostIP, uint16& hostPort ) { return eCLE_ServiceNotSupported; };
	virtual ECryLobbyError					SessionTerminateHostHintingForGroup(CrySessionHandle gh, SCryMatchMakingConnectionUID* pConnections, uint32 numConnections, CryLobbyTaskID* pTaskID, CryMatchmakingCallback pCB, void* pCBArg);
	virtual ECryLobbyError					SessionSetLocalFlags(CrySessionHandle gh, uint32 flags, CryLobbyTaskID* pTaskID, CryMatchmakingFlagsCallback pCB, void* pCBArg);
	virtual ECryLobbyError					SessionClearLocalFlags(CrySessionHandle gh, uint32 flags, CryLobbyTaskID* pTaskID, CryMatchmakingFlagsCallback pCB, void* pCBArg);
	virtual ECryLobbyError					SessionGetLocalFlags(CrySessionHandle gh, CryLobbyTaskID* pTaskID, CryMatchmakingFlagsCallback pCB, void* pCBArg);
	virtual bool										IsDead()const	{ return false; }

	virtual	ECryLobbyError					SendToServer(CCryLobbyPacket* pPacket, CrySessionHandle gh);
	virtual	ECryLobbyError					SendToAllClients(CCryLobbyPacket* pPacket, CrySessionHandle gh);
	virtual	ECryLobbyError					SendToClient(CCryLobbyPacket* pPacket, CrySessionHandle gh, SCryMatchMakingConnectionUID uid);
	virtual	ECryLobbyError					SessionSendRequestInfo(CCryLobbyPacket* pPacket, CrySessionID id, CryLobbyTaskID* pTaskID, CryMatchmakingSessionSendRequestInfoCallback pCB, void* pCBArg);
	virtual	ECryLobbyError					SessionSendRequestInfoResponse(CCryLobbyPacket* pPacket, CrySessionRequesterID requester);


	// The CrySessionHandle passed to the game consists of
	// the local session handle used to identify the session in other session calls
	// a uid generated by the host to identify the connection used.
	// This id is passed around when the host receives a connection so any peer can identify a connection from this.
	CrySessionHandle								CreateGameSessionHandle(CrySessionHandle h, SCryMatchMakingConnectionUID uid);
	SCryMatchMakingConnectionUID		CreateConnectionUID(CrySessionHandle h);
	CrySessionHandle								GetSessionHandleFromGameSessionHandle(CrySessionHandle gh);
	SCryMatchMakingConnectionUID		GetConnectionUIDFromGameSessionHandle(CrySessionHandle gh);
	virtual SCryMatchMakingConnectionUID	GetConnectionUIDFromGameSessionHandleAndChannelID(CrySessionHandle gh, uint16 channelID);
	uint16													GetChannelIDFromGameSessionHandle(CrySessionHandle gh);

	CrySessionHandle								GetCurrentNetNubSessionHandle(void) const;
	CrySessionHandle								GetCurrentHostedNetNubSessionHandle(void) const;
	virtual SCryMatchMakingConnectionUID	GetHostConnectionUID(CrySessionHandle gh);
	virtual ECryLobbyError SessionEnsureBestHost(CrySessionHandle gh, CryLobbyTaskID* pTaskID, CryMatchmakingCallback pCB, void* pCBArg) = 0;

	virtual void										TerminateHostMigration(CrySessionHandle gh);
	virtual eHostMigrationState			GetSessionHostMigrationState(CrySessionHandle gh);
	virtual ECryLobbyError					GetSessionPlayerPing( SCryMatchMakingConnectionUID uid, CryPing* const pPing );
	virtual ECryLobbyError					GetSessionIDFromSessionURL( const char* const pSessionURL, CrySessionID* const pSessionID ) { return eCLE_ServiceNotSupported; };
	virtual ECryLobbyError					GetSessionIDFromIP( const char* const pAddr, CrySessionID* const pSessionID ) { return eCLE_ServiceNotSupported; };

	virtual uint32									GetTimeSincePacketFromServerMS(CrySessionHandle gh);
	virtual void										DisconnectFromServer(CrySessionHandle gh);
	virtual void										StatusCmd(eStatusCmdMode mode);
	virtual void										KickCmd(CryLobbyConnectionID cxID, uint64 userID, const char* pName, EDisconnectionCause cause);
	virtual void										Kick(const CryUserID* pUserID, EDisconnectionCause cause) {};
	virtual void										BanCmd(uint64 userID, float timeout) {};
	virtual void										UnbanCmd(uint64 userID) {};
	virtual void										BanStatus(void) {};
	virtual void										Ban(const CryUserID* pUserID, float timeout) {};

#if RESET_CONNECTED_CONNECTION
	void														ResetConnection(CryLobbyConnectionID id);
#endif

#if NETWORK_HOST_MIGRATION
	virtual bool										HostMigrationInitiate(CrySessionHandle h, EDisconnectionCause cause);
	virtual void										HostMigrationInitialise(CrySessionHandle h, EDisconnectionCause cause) = 0;
	virtual void										HostMigrationReset(CrySessionHandle h);
	virtual void										HostMigrationFinalise(CrySessionHandle h) = 0;
	virtual void										HostMigrationWaitForNewServer(CrySessionHandle h);
	virtual ECryLobbyError					HostMigrationServer(SHostMigrationInfo* pInfo) = 0;
	virtual bool										IsSessionMigrated(SHostMigrationInfo* pInfo);
	virtual bool										GetNewHostAddress(char* address, SHostMigrationInfo* pInfo) = 0;
	virtual bool										AreAnySessionsMigrating(void);
	virtual bool										IsSessionMigrating(CrySessionHandle gh);
	bool														IsNubSessionMigrating() const;
	bool														IsNubSessionMigratable() const;
	void														HostMigrationStoreNetContextState(_smart_ptr<CNetContextState> &pNetContextState);
	_smart_ptr<CNetContextState>		HostMigrationGetNetContextState();
	void														HostMigrationResetNetContextState();
	void														OnReconnectClient(SHostMigrationInfo& hostMigrationInfo);
#if ENABLE_HOST_MIGRATION_STATE_CHECK
	void														HostMigrationStateCheck(CrySessionHandle h);
	void														HostMigrationStateCheckAddDataItem(SHostMigrationStateCheckData* pData);
#endif

protected:
	void														MarkHostHintInformationDirty(CrySessionHandle h) { m_sessions[h]->localFlags |= CRYSESSION_LOCAL_FLAG_HOST_HINT_INFO_DIRTY; }
	void														MarkHostHintExternalInformationDirty(CrySessionHandle h) { m_sessions[h]->localFlags |= CRYSESSION_LOCAL_FLAG_HOST_HINT_EXTERNAL_INFO_DIRTY; }
	void														SendHostHintInformation(CrySessionHandle h);
	virtual ECryLobbyError					SendHostHintExternal(CrySessionHandle h) { return eCLE_Success; }
	static void											HostHintingOverrideChanged(ICVar* pCVar);
#if ENABLE_HOST_MIGRATION_STATE_CHECK
	void														HostMigrationStateCheckInitialise(CrySessionHandle h);
	void														HostMigrationStateCheckNT(CrySessionHandle h);
	void														HostMigrationStateCheckLogDataItem(SHostMigrationStateCheckData* pData);
	bool														HostMigrationStateCheckCompareDataItems(SHostMigrationStateCheckData* pData1, SHostMigrationStateCheckData* pData2);
	bool														HostMigrationStateCheckWriteDataItemToPacket(CCryLobbyPacket* pPacket, SHostMigrationStateCheckData* pData);
	bool														HostMigrationStateCheckReadDataItemFromPacket(CCryLobbyPacket* pPacket, SHostMigrationStateCheckData* pData);
	void														ProcessHostMigrationStateCheckData(const TNetAddress& addr, CCrySharedLobbyPacket* pPacket);
#endif
public:
#endif

protected:
	enum EMMTask
	{
		eT_SessionTerminateHostHintingForGroup,
		eT_SessionModifyLocalFlags,

		eT_MatchMakingPlatformSpecificTask
	};

#if NETWORK_HOST_MIGRATION
	struct SHostHintInformation
	{
		SHostHintInformation(void) { Reset(); }
		CTimeValue lastSendInternal;
		CTimeValue lastSendExternal;
		CryPing	ping;
		uint8		natType;
		uint8		numActiveConnections;

		bool operator==( const SHostHintInformation& other ) const;
		bool operator!=( const SHostHintInformation& other ) const;

		void Reset(void);
		bool SufficientlyDifferent(const SHostHintInformation& other, CTimeValue currentTime);
	};

#if ENABLE_HOST_MIGRATION_STATE_CHECK
	typedef std::vector<SHostMigrationStateCheckData> THostMigrationStateCheckDatas;
#endif
#endif

	struct  SSession
	{
		virtual ~SSession() {};
		virtual const char* GetLocalUserName(uint32 localUserIndex) const = 0;

		struct SLConnection
		{
#if ENABLE_HOST_MIGRATION_STATE_CHECK
			THostMigrationStateCheckDatas	hostMigrationStateCheckDatas;
#endif
			SCryMatchMakingConnectionUID	uid;
			CryPing											pingToServer;
			uint8												numUsers;
			bool												used;
		}*														localConnection;

		struct SRConnection 
		{
			bool												TimerStarted()			{ return timerStarted; }
			void												StartTimer()				{ timerStarted = true; timer = g_time; }
			void												StopTimer()					{ timerStarted = false; }
			int64												GetTimer()					{ return g_time.GetMilliSecondsAsInt64() - timer.GetMilliSecondsAsInt64(); }

			CTimeValue									timer;
			CryLobbyConnectionID				connectionID;
			SCryMatchMakingConnectionUID	uid;
#if NETWORK_HOST_MIGRATION
			SHostHintInformation				hostHintInfo;
#if ENABLE_HOST_MIGRATION_STATE_CHECK
			THostMigrationStateCheckDatas	hostMigrationStateCheckDatas;
#endif
#endif

			struct SData
			{
				TMemHdl					data;
				uint32					dataSize;
			};

			MiniQueue<SData, GAME_RECV_QUEUE_SIZE>	gameRecvQueue;

			CryPing											pingToServer;
			uint8												numUsers;
			bool												used;
			bool												timerStarted;
			bool												gameInformedConnectionEstablished;
		}*														remoteConnection[MAX_LOBBY_CONNECTIONS];
#if NETWORK_HOST_MIGRATION
		SHostMigrationInfo_Private		hostMigrationInfo;
		SRConnection*									newHostPriority[MAX_LOBBY_CONNECTIONS];
		uint32												newHostPriorityCount;
		SCryMatchMakingConnectionUID	newHostUID;
		SHostHintInformation					hostHintInfo;
		SHostHintInformation					outboundHostHintInfo;
#endif
		CTimeValue										pingToServerTimer;
		CryMatchMakingConnectionID		hostConnectionID;
		uint32												createFlags;
		uint32												localFlags;
	};

	struct  STask
	{
		bool													TimerStarted()			{ return timerStarted; }
		void													StartTimer()				{ timerStarted = true; timer = g_time; }
		int64													GetTimer()					{ return g_time.GetMilliSecondsAsInt64() - timer.GetMilliSecondsAsInt64(); }

		CTimeValue										timer;
		CryLobbyTaskID								lTaskID;
		ECryLobbyError								error;
		CryMatchMakingTaskID					returnTaskID;
		uint32												startedTask;
		uint32												subTask;
		CrySessionHandle							session;
		void*													cb;
		void*													cbArg;
		CryLobbySendID								sendID;
		ECryLobbyError								sendStatus;
		TMemHdl												params[MAX_MATCHMAKING_PARAMS];
		uint32												numParams[MAX_MATCHMAKING_PARAMS];
		bool													used;
		bool													running;
		bool													timerStarted;
		bool													canceled;
	};

	void														StartTaskRunning(CryMatchMakingTaskID mmTaskID);
	void														TickBaseTask(CryMatchMakingTaskID mmTaskID);
	void														EndTask(CryMatchMakingTaskID mmTaskID);
	void														StopTaskRunning(CryMatchMakingTaskID mmTaskID);

	void														Tick(CTimeValue tv);
	ECryLobbyError									StartTask(uint32 etask, bool startRunning, CryMatchMakingTaskID* mmTaskID, CryLobbyTaskID* lTaskID, CrySessionHandle h, void* cb, void* cbArg);
	void														StartSubTask(uint32 etask, CryMatchMakingTaskID mmTaskID);
	void														FreeTask(CryMatchMakingTaskID mmTaskID);
	ECryLobbyError									CreateTaskParam(CryMatchMakingTaskID mmTaskID, uint32 param, const void* paramData, uint32 numParams, size_t paramSize);
	ECryLobbyError									CreateSessionHandle(CrySessionHandle *h, bool host, uint32 createFlags, int numUsers);
	void														FreeSessionHandle(CrySessionHandle h);
	CryMatchMakingConnectionID			AddRemoteConnection(CrySessionHandle h, CryLobbyConnectionID connectionID, SCryMatchMakingConnectionUID uid, uint32 numUsers);
	virtual void										FreeRemoteConnection(CrySessionHandle h, CryMatchMakingConnectionID id);
	virtual uint64									GetSIDFromSessionHandle(CrySessionHandle h) = 0;
	bool														FindLocalConnectionFromUID(SCryMatchMakingConnectionUID uid, CrySessionHandle* h);
	bool														FindConnectionFromUID(SCryMatchMakingConnectionUID uid, CrySessionHandle* pH, CryMatchMakingConnectionID* pID);
	bool														FindConnectionFromSessionUID(CrySessionHandle h, SCryMatchMakingConnectionUID uid, CryMatchMakingConnectionID* pID);
	CryMatchMakingTaskID						FindTaskFromSendID(CryLobbySendID sendID);
	CryMatchMakingTaskID						FindTaskFromTaskTaskID(uint32 task, CryMatchMakingTaskID mmTaskID);
	CryMatchMakingTaskID						FindTaskFromTaskSessionHandle(uint32 task, CrySessionHandle h);
	CryMatchMakingTaskID						FindTaskFromLobbyTaskID(CryLobbyTaskID lTaskID);
	void														UpdateTaskError(CryMatchMakingTaskID mmTaskID, ECryLobbyError error);
	void														SessionDisconnectRemoteConnectionViaNub(CrySessionHandle h, CryMatchMakingConnectionID id, EDisconnectionCause cause, const char* reason, bool origin);
	void														SessionDisconnectRemoteConnection(CrySessionHandle h, CryMatchMakingConnectionID id, EDisconnectionCause cause, bool origin);
	void														StartSessionPendingGroupDisconnect(CryMatchMakingConnectionID mmTaskID);
	void														TickSessionPendingGroupDisconnect(CryMatchMakingConnectionID mmTaskID);
	void														StartSessionModifyLocalFlags(CryMatchMakingConnectionID mmTaskID);
	void														TickSessionModifyLocalFlags(CryMatchMakingConnectionID mmTaskID);

	ESocketError										Send(CryMatchMakingTaskID mmTaskID, CCryLobbyPacket* pPacket, CrySessionHandle h, const TNetAddress& to);
	ESocketError										Send(CryMatchMakingTaskID mmTaskID, CCryLobbyPacket* pPacket, CrySessionHandle h, CryMatchMakingConnectionID connectionID);
	void														SendToAll(CryMatchMakingTaskID mmTaskID, CCryLobbyPacket* pPacket, CrySessionHandle h, CryMatchMakingConnectionID skipID);
	void														SendToAllClientsNT(TMemHdl mh, uint32 length, CrySessionHandle h);
	void														SendToServerNT(TMemHdl mh, uint32 length, CrySessionHandle h);
	void														SendToClientNT(TMemHdl mh, uint32 length, CrySessionHandle h, SCryMatchMakingConnectionUID uid);
	void														SendServerPing(CrySessionHandle h);
	void														ProcessServerPing(const TNetAddress& addr, CCrySharedLobbyPacket* pPacket);
	void														DispatchUserPacket(TMemHdl mh, uint32 length, CrySessionHandle gh);
	void														InitialDispatchUserPackets(CrySessionHandle h, CryMatchMakingConnectionID connectionID);
	void														DispatchSessionUserDataEvent(ECryLobbySystemEvent event, TMemHdl mh);
	void														SessionUserDataEvent(ECryLobbySystemEvent event, CrySessionHandle h, SCryUserInfoResult* pUserInfo);
	TMemHdl													PacketToMemoryBlock(CCryLobbyPacket* pPacket, uint32& length);
	CCryLobbyPacket*								MemoryBlockToPacket(TMemHdl mh, uint32 length);

	void														DispatchSessionEvent(CrySessionHandle h, ECryLobbySystemEvent event);

	void														ProcessSessionDeleteRemoteConnection(const TNetAddress& addr, CCrySharedLobbyPacket* pPacket);
	static void											MatchmakingSessionJoinFromConsoleCallback( CryLobbyTaskID taskID, ECryLobbyError error, CrySessionHandle h, uint32 ip, uint16 port, void* pArg );
	virtual const char*							GetConnectionName(SSession::SRConnection* pConnection, uint32 localUserIndex) const { return NULL; }
	virtual uint64									GetConnectionUserID(SSession::SRConnection* pConnection, uint32 localUserIndex) const { return INVALID_USER_ID; }
#if NETWORK_HOST_MIGRATION
	void														ProcessHostHinting(const TNetAddress& addr, CCrySharedLobbyPacket* pPacket);
	bool														ProcessSingleHostHint(CrySessionHandle h, uint32 uid, SHostHintInformation &hintToUpdate, SHostHintInformation &incomingHint);
	void														SortNewHostPriorities(CrySessionHandle handle);
	static int											SortNewHostPriorities_Comparator(const void* pLHS, const void* pRHS);
	static int											SortNewHostPriorities_ComparatorHelper(const SHostHintInformation& LHS, const SHostHintInformation& RHS);
#if HOST_MIGRATION_SOAK_TEST_BREAK_DETECTION
	void														DetectHostMigrationTaskBreak(CrySessionHandle h, char* pLocation);
#endif

	CHostMigration									m_hostMigration;
#if ENABLE_HOST_MIGRATION_STATE_CHECK
	CrySessionHandle								m_hostMigrationStateCheckSession;
#endif
#endif
	TBannedUser											m_bannedUser;
	uint32													m_connectionUIDCounter;
	SSession*												m_sessions[MAX_MATCHMAKING_SESSIONS];
	STask*													m_task[MAX_MATCHMAKING_TASKS];
	CCryLobby*											m_lobby;
	CCryLobbyService*								m_pService;
	ECryLobbyService								m_serviceType;

	// CVars
	int															m_fullStatusOnClient;
};

#endif // __CRYMATCHMAKING_H__

